Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add managed decimal support (as in the rust framework) #477

Merged
merged 21 commits into from
Sep 10, 2024

Conversation

danielailie
Copy link
Contributor

@danielailie danielailie commented Sep 3, 2024

Added support for ManagedDecimal and ManagedDecimalSigned.

See:

@danielailie danielailie changed the title [WIP] Tool 223 add managed decimal support from rust Tool 223 add managed decimal support from rust Sep 5, 2024
@danielailie danielailie self-assigned this Sep 5, 2024
@popenta popenta self-requested a review September 5, 2024 11:00
@@ -10,18 +10,20 @@ export class Type {

private readonly name: string;
private readonly typeParameters: Type[];
protected readonly cardinality: TypeCardinality;
protected readonly metadata: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move the protected member last so that they are grouped by access modifiers?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, as discussed, the type can be string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -64,10 +66,18 @@ export class Type {
return this.typeParameters;
}

getMetadata(): any {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return type should be string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -1,18 +1,21 @@
export class TypeFormula {
name: string;
metadata: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe change type to string here, as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we will let any because is the general object

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you think it would be better to use object type instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be also a primitive that's why I think any is a better option here

const typeParameters = hasTypeParameters
? `<${this.typeParameters.map((tp) => tp.toString()).join(", ")}>`
: "";
const baseName = `${this.name}${typeParameters}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you intentionally left out the <>? It used to be: return ${this.name}<${typeParameters}>;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<> is already added when we build the variable typeParameters <${this.typeParameters.map((tp) => tp.toString()).join(", ")}>

const payload = buffer.slice(0, length);

const result = this.decodeTopLevel(payload, type);
const decodedLength = length;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

length can simply be used instead of creating a new variable, but not a big deal.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
return new ManagedDecimalValue(native[0], native[1]);
}
errorContext.throwError(`(function: toEnumValue) unsupported native type ${typeof native}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be (function: toManagedDecimal)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -184,6 +185,118 @@ describe("test smart contract interactor", function () {
assert.isTrue(typedBundle.returnCode.equals(ReturnCode.Ok));
});

it("should interact with 'basic-features' (local testnet) using the SmartContractTransactionsFactory", async function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not using SmartContractTransactionsFactory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


decodeTopLevel(buffer: Buffer, type: ManagedDecimalType): ManagedDecimalValue {
if (buffer.length === 0) {
return new ManagedDecimalValue(new BigNumber(0), 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic number 2 can be a constant - is it default scale of managed decimal?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const isUsize = type.getMetadata() === "usize";

if (isUsize) {
const u32Size = 4;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


encodeNested(value: ManagedDecimalValue): Buffer {
let buffers: Buffer[] = [];
if (value.getType().getMetadata() == "usize") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's worth to add a function on ManagedDecimalValue, such as isVariable()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can also say (optional):

const valueBuffer = uffer.from(this.binaryCodec.encodeTopLevel(new BigUIntValue(value.valueOf());

if (!value.isVariable) {
    return valueBuffer;
}

const scaleBuffer = Buffer.from(this.binaryCodec.encodeNested(new U32Value(value.getScale()));
return Buffer.concat([...]);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not working becauze if is not variable the value is encodet top level if is variable the value is encodedNested

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, I add it on type and use it here


const isUsize = type.getMetadata() === "usize";

if (isUsize) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth to add maybe a function on ManagedDecimalType, such as type.isVariable()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

isGenericType(): boolean {
return this.typeParameters.length > 0;
}

hasMetadata(): boolean {
return this.metadata !== null && this.metadata !== undefined;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, !! operator 🙈 (the "bang, bang, you're a boolean operator").

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return false;
}

return this.value == other.value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also compare scale?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need to compare the scale in order to compare the values, I added some tests to test it

return false;
}

return this.value == other.value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work when comparing instances of BigNumber?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it works

/**
* Returns whether two objects have the same value.
*/
equals(other: ManagedDecimalSignedValue): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same questions as above.

}

toString(): string {
return this.value.toFixed(this.scale);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have some small unit tests for ManagedDecimalValue and ManagedDecimalSignedValue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

this.learnedTypesMap.delete(type.getName());
this.learnedTypesMap.set(type.getName(), type);
if (type.getName() === "ManagedDecimal" || type.getName() === "ManagedDecimalSigned") {
this.learnedTypesMap.delete(type.getName());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed separately, fix is needed here.

CiprianDraghici
CiprianDraghici previously approved these changes Sep 9, 2024
}

private getFullNameForGeneric(): string {
const hasTypeParameters = this.getTypeParameters.length > 0;
const hasTypeParameters = this.getTypeParameters().length > 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.isGenericType() could have been used, but this does the same thing.

popenta
popenta previously approved these changes Sep 9, 2024
Copy link
Contributor

@andreibancioiu andreibancioiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The equals() function should be fixed. All the others are optional comments and ideas.

@@ -81,9 +81,10 @@ export class TypeFormulaParser {
private acquireTypeWithParameters(stack: any[]): TypeFormula {
const typeParameters = this.acquireTypeParameters(stack);
const typeName = stack.pop();
const metadata = typeParameters[0].name;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be more explicit that we deal with metadata only for some types, variable should be defined under the scope of if managed decimal (I know it works the same).

Optional, can also stay as it is.

}

encodeNested(value: ManagedDecimalValue): Buffer {
let buffers: Buffer[] = [];
if (value.getType().getMetadata() == "usize") {
const managedDecimalType = value.getType() as ManagedDecimalType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we add isVariable() not only to the type, but also to the value, we work around without type assertions.

return this.metadata;
}

isVariable(): boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful both on type and value.

constructor(value: BigNumber.Value, scale: number) {
super(new ManagedDecimalType(scale));
constructor(value: BigNumber.Value, scale: number, isVariableDecimals: boolean = false) {
super(new ManagedDecimalSignedType(isVariableDecimals ? "usize" : scale));
Copy link
Contributor

@andreibancioiu andreibancioiu Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also in ManagedDecimalValue - is it worth to encapsulate this tricky logic somewhere? E.g. named constructor of the types? 🙈

@@ -84,8 +89,8 @@ export class ManagedDecimalSignedValue extends TypedValue {
private readonly value: BigNumber;
private readonly scale: number;

constructor(value: BigNumber.Value, scale: number) {
super(new ManagedDecimalType(scale));
constructor(value: BigNumber.Value, scale: number, isVariableDecimals: boolean = false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter name isVariableDecimals is not consistent with parameter name of ManagedDecimalValue constructor. Maybe isVariable for both of them?

@@ -26,6 +26,6 @@ export class TypeExpressionParser {

private typeFormulaToType(typeFormula: TypeFormula): Type {
const typeParameters = typeFormula.typeParameters.map((typeFormula) => this.typeFormulaToType(typeFormula));
return new Type(typeFormula.name, typeParameters, undefined, typeFormula.metadata);
return new Type(typeFormula.name, typeParameters, TypeCardinality.fixed(1), typeFormula.metadata);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wrong before. Maybe undefined is better here (if all tests still pass). Cardinality == 1 is tricky (it's a white lie), since here we also create & return variadic types.

Can be either undefined of fixed(1) - since the latter is the default anyway. When applying the Python-like simplifications (in the not very close future), this will get simplified (no cardinality abstraction etc.).

Comment on lines 23 to 24
let firstValue = new ManagedDecimalValue(new BigNumber(1), 2, false);
let secondValue = new ManagedDecimalValue(new BigNumber(2), 2, false);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here / above / below - can also be const.

});

it("should set the correct scale when variable decimals", () => {
let value = new ManagedDecimalValue(new BigNumber(1.3), 2, true);
Copy link
Contributor

@andreibancioiu andreibancioiu Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not thinking of this before - how about:

new ManagedDecimalValue("42.43", 8);
new ManagedDecimalValue("42.43", "usize");

Using the trick type ManagedDecimalScale = number | "usize".

let firstValue = new ManagedDecimalValue(new BigNumber(1.234), 3, false);
let secondValue = new ManagedDecimalValue(new BigNumber(12.34), 2, false);

assert.isFalse(firstValue.equals(secondValue));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have a "positive" equality test / equal values, as well.

});

it("should compare correctly two managed decimals even with different scale", () => {
let firstValue = new ManagedDecimalValue(new BigNumber(1.234), 3, false);
Copy link
Contributor

@andreibancioiu andreibancioiu Sep 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For documentation purposes, when a BigNumber is created from non-integers, let's pass them as a string. Maybe in our examples, drop the BigNumber wrapping e.g.

new ManagedDecimalValue("1.234", 3, false);

Instead of:

new ManagedDecimalValue(new BigNumber(...), 3, false);

Since in the constructor of ManagedDecimalValue this is handled correctly anyway.

@danielailie danielailie merged commit b8927c6 into main Sep 10, 2024
3 checks passed
@danielailie danielailie deleted the TOOL-223-add-managed-decimal-support-from-rust branch September 10, 2024 12:20
@andreibancioiu andreibancioiu changed the title Tool 223 add managed decimal support from rust Add managed decimal support (as in the rust framework) Sep 10, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants